﻿using Fast.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Routing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Fast.DynamicApiController
{
    /// <summary>
    /// 定义动态Api控制器
    /// </summary>
    public class DynamicApiControllerConvention : IApplicationModelConvention
    {
        private readonly DynamicApiControllerOptions _options;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <exception cref="ArgumentException"></exception>
        public DynamicApiControllerConvention()
        {
            _options = App.GetConfig<DynamicApiControllerOptions>(nameof(DynamicApiControllerOptions).Replace("Options", ""), true)
                ?? throw new ArgumentException("DynamicApiControllerOptions 不能为空", nameof(_options));
        }

        public void Apply(ApplicationModel application)
        {
            foreach (var controller in application.Controllers)
            {
                var type = controller.ControllerType.AsType();
                var dynamicApiControllerAttribute = type.GetCustomAttribute<DynamicApiControllerAttribute>();

                if (!typeof(IDynamicApiController).GetTypeInfo().IsAssignableFrom(type) && dynamicApiControllerAttribute == null)
                    continue;

                //配置控制器
                ConfigureController(controller, dynamicApiControllerAttribute);
            }
        }

        /// <summary>
        /// 配置动态api控制器
        /// </summary>
        /// <param name="controller"></param>
        private void ConfigureController(ControllerModel controller, DynamicApiControllerAttribute dynamicApiControllerAttribute)
        {
            // 配置区域
            ConfigureArea(controller, dynamicApiControllerAttribute);

            // 配置控制器接口可见性
            ConfigureControllerApiExplorer(controller);

            // 配置控制器名称
            ConfigureControllerName(controller);

            // 配置控制器路由特性
            ConfigureControllerRouteAttribute(controller);

            // 配置控制器下的所有动作方法
            foreach (var action in controller.Actions)
            {
                ConfigureAction(action);
            }
        }
        #region 配置区域
        /// <summary>
        /// 配置区域
        /// </summary>
        /// <param name="controller"></param>
        /// <param name="dynamicApiControllerAttribute"></param>
        private void ConfigureArea(ControllerModel controller, DynamicApiControllerAttribute dynamicApiControllerAttribute)
        {
            if (controller.RouteValues.ContainsKey("area")) return;

            var area = dynamicApiControllerAttribute?.Area ?? _options.DefaultArea;
            if (area.IsNullOrWhiteSpace()) return;

            controller.RouteValues["area"] = area;
        }
        #endregion

        #region 配置控制器接口可见性
        private void ConfigureControllerApiExplorer(ControllerModel controller)
        {
            controller.ApiExplorer.IsVisible ??= true;
        }
        #endregion

        #region 配置控制器名称
        private void ConfigureControllerName(ControllerModel controller)
        {
            controller.ControllerName = controller.ControllerName.RemovePostFix(_options.RemoveControllerPostfixes.ToArray());
        }

        #endregion

        #region 配置控制器路由特性
        private void ConfigureControllerRouteAttribute(ControllerModel controller)
        {

        }
        #endregion

        #region 配置动作方法
        private void ConfigureAction(ActionModel action)
        {
            // 配置动作方法接口可见性
            ConfigureActionApiExplorer(action);

            // 配置动作方法名称
            ConfigureActionName(action);

            // 配置动作方法请求谓词特性
            ConfigureActionHttpMethodAttribute(action);

            // 配置引用类型参数
            ConfigureClassTypeParameter(action);

            // 配置动作方法路由特性
            ConfigureActionRouteAttribute(action);
        }

        #region 配置动作方法接口可见性
        /// <summary>
        /// 配置动作方法接口可见性
        /// </summary>
        /// <param name="action"></param>
        private void ConfigureActionApiExplorer(ActionModel action)
        {
            action.ApiExplorer.IsVisible ??= true;
        }

        #endregion

        #region 配置动作方法名称
        /// <summary>
        /// 配置动作方法名称
        /// </summary>
        /// <param name="action"></param>
        private void ConfigureActionName(ActionModel action)
        {
            // 删除指定后缀
            var actionName = action.ActionName.RemovePostFix(_options.RemoveActionPostfixes.ToArray());

            if (_options.HasRemoveActionPrefix.Value)
            {
                //处理动作方法名称谓词
                var key = Auxiliary.HttpVerbs.FirstOrDefault(p => actionName.ToLower().IndexOf(p.Key) == 0).Key;
                if (key.IsNotNullOrWhiteSpace())
                {
                    actionName = actionName[key.Length..];
                }
            }

            action.ActionName = actionName;
        }
        #endregion

        #region 配置动作方法请求谓词特性
        /// <summary>
        /// 配置动作方法请求谓词特性
        /// </summary>
        /// <param name="action"></param>
        private void ConfigureActionHttpMethodAttribute(ActionModel action)
        {
            var selectorModel = action.Selectors[0];
            // 跳过已配置请求谓词特性的配置
            if (selectorModel.ActionConstraints.Count > 0) return;

            //获取
            var verb = Auxiliary.HttpVerbs.FirstOrDefault(p => action.ActionMethod.Name.ToLower().IndexOf(p.Key) == 0).Value ?? _options.DefaultHttpVerb;

            // 添加请求约束
            selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb }));

            // 添加请求谓词特性
            HttpMethodAttribute httpMethodAttribute = verb switch
            {
                "GET" => new HttpGetAttribute(),
                "POST" => new HttpPostAttribute(),
                "PUT" => new HttpPutAttribute(),
                "DELETE" => new HttpDeleteAttribute(),
                _ => throw new ArgumentException($"不支持的 HTTP 谓词: {verb}")
            };

            selectorModel.EndpointMetadata.Add(httpMethodAttribute);
        }
        #endregion

        #region 处理类类型参数（添加[FromBody] 特性）
        /// <summary>
        /// 处理类类型参数（添加[FromBody] 特性）
        /// </summary>
        /// <param name="action"></param>
        private void ConfigureClassTypeParameter(ActionModel action)
        {
            if (action.Parameters.IsNullOrEmpty()) return;

            foreach (var parameterModel in action.Parameters)
            {
                // 如果参数已有绑定特性，则跳过
                if (parameterModel.BindingInfo != null) continue;

                var parameterType = parameterModel.ParameterInfo.ParameterType;
                // 如果是基元类型，则跳过
                if (parameterType.IsPrimitive()) continue;

                // 如果是文件类型，则跳过
                if (typeof(IFormFile).IsAssignableFrom(parameterType) || typeof(IFormFileCollection).IsAssignableFrom(parameterType)) continue;

                if (CanUseFormBodyBinding(action))
                {
                    parameterModel.BindingInfo = BindingInfo.GetBindingInfo(new[] { new FromBodyAttribute() });
                }
            }
        }

        /// <summary>
        /// 是否可以使用 <paramref name="FormBody"/> 特性
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        private bool CanUseFormBodyBinding(ActionModel action)
        {
            foreach (var selector in action.Selectors)
            {
                if (selector.ActionConstraints == null)
                {
                    continue;
                }

                foreach (var actionConstraint in selector.ActionConstraints)
                {

                    var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint;
                    if (httpMethodActionConstraint == null)
                    {
                        continue;
                    }

                    if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsExists("GET", "DELETE", "TRACE", "HEAD")))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        #endregion

        #region 配置动作方法路由特性
        /// <summary>
        /// 配置动作方法路由特性
        /// </summary>
        /// <param name="action"></param>
        private void ConfigureActionRouteAttribute(ActionModel action)
        {
            var selectorModel = action.Selectors[0];
            // 跳过已配置路由特性的配置
            if (selectorModel.AttributeRouteModel != null) return;

            var controllerRouteModel = action.Controller.Selectors[0].AttributeRouteModel;
            if (controllerRouteModel != null) return;

            // 拼接路由
            selectorModel.AttributeRouteModel = CreateActionRouteModel(action);
        }

        private AttributeRouteModel CreateActionRouteModel(ActionModel action)
        {
            var area = action.Controller.RouteValues.TryGetValue("area", out var areaName) ? areaName : string.Empty;

            var controllerName = action.Controller.ControllerName;

            var actionName = action.ActionName.Length == 0 ? null : action.ActionName;
            var paramRouteTemplates = GenerateParameterRouteTemplates(action);
            var paramRouteTemplate = default(string);
            if (paramRouteTemplates != null)
            {
                paramRouteTemplate = paramRouteTemplates.Count == 0 ? null : string.Join('/', paramRouteTemplates);
            }

            var route = $"/{_options.DefaultRoutePrefix}/{area}/{controllerName}/{actionName}/{paramRouteTemplate}".Replace("//", "/");
            route = _options.HasLowercaseRoute.Value ? route.ToLower() : _options.HasCamelCase.Value ? route.ToCamelCase() : route;

            return new AttributeRouteModel(new RouteAttribute(route));
        }

        /// <summary>
        /// 生成参数路由模板
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        private List<string> GenerateParameterRouteTemplates(ActionModel action)
        {
            // 如果没有参数，则跳过
            if (action.Parameters.Count == 0) return default;

            var parameters = action.Parameters;

            var parameterRouteTemplate = new List<string>();

            // 遍历所有参数
            foreach (var parameterModel in parameters)
            {
                var parameterType = parameterModel.ParameterType;
                var parameterAttributes = parameterModel.Attributes;

                if (_options.HasLowercaseRoute.Value)
                {
                    parameterModel.ParameterName = parameterModel.ParameterName.ToLower();
                }

                // 判断是否贴有任何 [FromXXX] 特性了
                var hasFormAttribute = parameterAttributes.Any(u => typeof(IBindingSourceMetadata).IsAssignableFrom(u.GetType()));

                // 如果没有贴 [FromRoute] 特性且不是基元类型，则跳过
                // 如果没有贴 [FromRoute] 特性且有任何绑定特性，则跳过
                if (!parameterAttributes.Any(u => u is FromRouteAttribute)
                    && (!parameterType.IsPrimitive() || hasFormAttribute)) continue;

                var hasApiControllerAttribute = action.Controller.Attributes.Any(u => u.GetType() == typeof(ApiControllerAttribute));
                if (hasApiControllerAttribute) continue;

                var template = $"{{{parameterModel.ParameterName}}}";

                parameterRouteTemplate.Add(template);
            }

            return parameterRouteTemplate;

        }
        #endregion
        #endregion
    }
}
